#include <iostream>
#include <time.h>
#include <gl\glew.h>
#include <gl\freeglut.h>
#include "..\CameraGL\CameraGL.h"
#include "..\CustomMouseClass\CustomMouseClass.h"
#include "..\Geometry\Geometry.h"
#include "..\CelestialBody\CelestialBody.h"

using namespace std;

MouseClass glob_UserMouse;
CameraGL glob_UserCamera;

double glob_SunRadius = 20;
CelestialBody * glob_Sun = new CelestialBody(glob_SunRadius, 0, 
	1, 1, 0);

bool glob_KeyStates[256];

float glob_SunLightColor[3] = {1.0, 1.0, 1.0},
	glob_SunLightPos[4] = {0.0, 0.0, 0.0, 1.0};

double glob_AngleOfView_degs = 45,
	glob_NearPlaneDist = 1.0,
	glob_FarPlaneDist = 10000.0;


void HandleMouse ()
{
	if (glob_UserMouse.IsLeftButtonDown())
	{
		glob_UserCamera.MoveForward(0.01);
	}

	if (glob_UserMouse.IsRightButtonDown())
	{
		glob_UserCamera.MoveForward(-0.01);
	}

	double mouseDeltaX = glob_UserMouse.GetCurrentPosition().GetX() - glob_UserMouse.GetLastPosition().GetX();
	double mouseDeltaY = -(glob_UserMouse.GetCurrentPosition().GetY() - glob_UserMouse.GetLastPosition().GetY());

	if(mouseDeltaX > 0)
	{
		glob_UserCamera.LookRight (mouseDeltaX);
	}
	else
	{
		glob_UserCamera.LookLeft (-mouseDeltaX);
	}

	if(mouseDeltaY > 0)
	{
		glob_UserCamera.LookUp (mouseDeltaY);
	}
	else
	{
		glob_UserCamera.LookDown (-mouseDeltaY);
	}
}

void ApplyCamera()
{
	glob_UserCamera.ApplyLook();
}

void DrawObjects()
{
	CelestialBody::Render (glob_Sun);
}

void ApplyLights()
{
	glLightfv(GL_LIGHT0, GL_DIFFUSE, glob_SunLightColor);
	glLightfv(GL_LIGHT0, GL_POSITION, glob_SunLightPos);
}

void Display()
{
	glClearColor (0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	ApplyCamera();

	DrawObjects();

	glutSwapBuffers();
}

void UpdateObjects()
{
	CelestialBody::Update (glob_Sun);
}

void MainLoop()
{
	UpdateObjects(); 
	Display();
	HandleMouse();
	glob_UserMouse.OverrideLastPosition (glob_UserMouse.GetCurrentPosition());
}

void ReshapeWindow(int width, int height)
{
	glViewport(0, 0, width, height);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity();

	double aspectRatio = ((float)width) / ((float)height);

	gluPerspective(glob_AngleOfView_degs, aspectRatio, glob_NearPlaneDist, glob_FarPlaneDist);
	glMatrixMode (GL_MODELVIEW);
}

void HandleMouseButtons (int button, int state, int x, int y)
{
	if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
	{
		glob_UserMouse.PressLeftButton();
	}
	if(button == GLUT_LEFT_BUTTON && state == GLUT_UP)
	{
		glob_UserMouse.ReleaseLeftButton();
	}

	if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
	{
		glob_UserMouse.PressRightButton();
	}
	if(button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
	{
		glob_UserMouse.ReleaseRightButton();
	}
}	

void HandlePassiveMovement (int x, int y)
{
	glob_UserMouse.SetCurrentPosition ( VectorMath::Vector3(x, y));
}

void HandleActiveMovement (int x, int y)
{
	glob_UserMouse.SetCurrentPosition ( VectorMath::Vector3(x, y));
}

void HandleKeyDown (unsigned char key, int x, int y)
{
	glob_KeyStates[key] = true;
}

void HandleKeyUp (unsigned char key, int x, int y)
{
	glob_KeyStates[key] = false;
}

void InitializeWindow()
{
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
	glutInitWindowPosition (0, 0);
	glutInitWindowSize (800, 800);
	glutCreateWindow ("Solar System");
}

void RegisterCallbacks()
{
	glutDisplayFunc (Display);
	glutIdleFunc (MainLoop);
	glutKeyboardFunc (HandleKeyDown);
	glutKeyboardUpFunc (HandleKeyUp);
	glutMouseFunc (HandleMouseButtons);
	glutPassiveMotionFunc (HandlePassiveMovement);
	glutMotionFunc (HandleActiveMovement);
	glutReshapeFunc (ReshapeWindow);
}

void InitializeGL()
{
	glEnable (GL_DEPTH);
	glEnable (GL_DEPTH_TEST);
	glEnable (GL_COLOR_MATERIAL);
	glEnable (GL_LIGHTING);
	glEnable (GL_LIGHT0);
}

void InitializeWorld()
{
	glob_UserCamera.SetPosition (VectorMath::Vector3 (30, 0, 30));
	double planetRadius = 2;
	double planetAngularVelocity_degsPerFrame = 0.01;
	double planetColorRed = 1.0;
	double planetColorGreen = 0.0;
	double planetColorBlue = 0.0;
	CelestialBody * planet = new CelestialBody(planetRadius, planetAngularVelocity_degsPerFrame, 
		planetColorRed, planetColorGreen, planetColorBlue);
	glob_Sun->AddSatellite (planet, VectorMath::Vector3 (40, 0, 0));

	CelestialBody * planet2 = new CelestialBody(planetRadius*2, planetAngularVelocity_degsPerFrame / 5, 
		0, 0, 1.0);
	glob_Sun->AddSatellite (planet2, VectorMath::Vector3 (-40*2, 0, 10));

	double moonRadius = 0.5;
	double moonAngularVelocity_degsPerFrame = planetAngularVelocity_degsPerFrame * (-8);
	double moonColorRed = 1.0;
	double moonColorGreen = 1.0;
	double moonColorBlue = 1.0;

	CelestialBody * moon = new CelestialBody(moonRadius, moonAngularVelocity_degsPerFrame,
		moonColorRed, moonColorGreen, moonColorBlue);

	planet->AddSatellite (moon, VectorMath::Vector3 (planetRadius*2, 0, 0));

	/*
	This code generates random planets, uncomment and compile if you wish to test it out
	Note: planets may overlap, this code does not ensure that planets have different trajectories

	//generating random planets
	int numPlanetsToGenerate = 12;
	srand(clock()); //randomizing the random generation
	for (int i=0; i<numPlanetsToGenerate; i++)
	{
		//Generating a random decimal number from 0.3 to 7
		int randomUpperBound = 70;
		int randomNumber = rand() % randomUpperBound;
		double randomRadiusMultiplier = randomNumber / 10.0;
		if(randomRadiusMultiplier < 0.3)
		{
			randomRadiusMultiplier = 1.0;
		}
		//--------------

		double randomRadius = planetRadius * randomRadiusMultiplier,
			xOffset = (glob_SunRadius + randomRadius) * 1.1, // ensuring that
			zOffset = (glob_SunRadius + randomRadius) * 1.1; // the randomly generated planet does not intersect the sun

		CelestialBody * randomPlanet = new CelestialBody (randomRadius, (1 + rand() % 10) / 100.0, //random planet, random angular velocity
			(rand()%10)/10.0, (rand()%10)/10.0, (rand()%10)/10.0 ); //random planet color (range 0.0 - 0.9)
		glob_Sun->AddSatellite (randomPlanet, VectorMath::Vector3 (xOffset + rand()%100, 0, zOffset + rand()%100)); // random ZX offset (range 0-99)
	}
	*/
}

int main(int argc, char ** argv)
{
	glutInit(&argc, argv);
	InitializeWindow();
	RegisterCallbacks();

	InitializeGL();
	InitializeWorld();

	glutMainLoop();

	return 0;
}